home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Nebula 2
/
Nebula Two.iso
/
SourceCode
/
MiniExamples
/
AppKit
/
TextORama
/
EmacsText.m
< prev
next >
Wrap
Text File
|
1995-06-12
|
10KB
|
428 lines
/* EmacsText.m
*
* EmacsText is a subclass of Text which adds support for
* keyboard bindings commonly used by the emacs editor.
*
* You may freely copy, distribute, and reuse the code in this example.
* NeXT disclaims any warranty of any kind, expressed or implied, as to its
* fitness for any particular use.
*
* Written by: Julie Zelenski
* Created: Sept/91
*/
#import "EmacsText.h"
@implementation EmacsText
/** This is the charCode offset for Control keys from Encoding Vectors Tech Doc **/
#define CONTROL_OFFSET (unsigned short)0x40
/** Cursor Movement Commands **/
#define CTRL_A ('A' - CONTROL_OFFSET)
#define CTRL_B ('B' - CONTROL_OFFSET)
#define CTRL_E ('E' - CONTROL_OFFSET)
#define CTRL_F ('F' - CONTROL_OFFSET)
#define CTRL_N ('N' - CONTROL_OFFSET)
#define CTRL_P ('P' - CONTROL_OFFSET)
#define ALT_LESS ((unsigned short)0xa3)
#define ALT_GREATER ((unsigned short) 0xb3)
#define ALT_B ((unsigned short) 0xe5)
#define ALT_F ((unsigned short) 0xa6)
/** Delete Commands **/
#define CTRL_D ('D' - CONTROL_OFFSET)
#define CTRL_K ('K' - CONTROL_OFFSET)
#define CTRL_O ('O' - CONTROL_OFFSET)
#define CTRL_Y ('Y' - CONTROL_OFFSET)
#define ALT_D ((unsigned short) 0x44)
#define ALT_H ((unsigned short) 0xe3)
typedef struct _sel
{
unsigned short charCode;
SEL selector;
SEL positionSelector;
char *selectorString;
char *positionSelectorString;
} SelectorItem;
static SelectorItem emacsMetaKeys[] =
{
{ALT_B, 0, 0, "moveToPosition:", "positionForWordBegin"},
{ALT_F, 0, 0, "moveToPosition:", "positionForWordEnd"},
{ALT_LESS, 0, 0, "moveToPosition:", "positionForDocumentBegin"},
{ALT_GREATER, 0, 0, "moveToPosition:", "positionForDocumentEnd"},
{ALT_D, 0, 0, "deleteToPosition:", "positionForWordEnd"},
{ALT_H, 0, 0, "deleteToPosition:", "positionForWordBegin"},
{0}
};
static SelectorItem emacsControlKeys[] =
{
{CTRL_A, 0, 0, "moveToPosition:", "positionForLineBegin"},
{CTRL_E, 0, 0, "moveToPosition:", "positionForLineEnd"},
{CTRL_K, 0, 0, "deleteToLineEnd", 0},
{CTRL_D, 0, 0, "deleteToPosition:", "nextPositionIfEmpty"},
{CTRL_Y, 0, 0, "yank", 0},
{0}
};
unsigned short emacsFilter (unsigned short
charCode, int flags, unsigned short charSet)
{
if (flags & NX_CONTROLMASK)
{
switch(charCode) {
case CTRL_F:
return NX_RIGHT;
case CTRL_B:
return NX_LEFT;
case CTRL_N:
return NX_DOWN;
case CTRL_P:
return NX_UP;
default: break;
}
}
return NXEditorFilter(charCode, flags, charSet);
}
int GetPrevious(NXStream *s)
{
int pos;
int ch;
pos = NXTell(s);
if (pos <= 0) return EOF;
NXSeek(s, --pos, NX_FROMSTART);
ch = NXGetc(s);
NXUngetc(s);
return ch;
}
// Complete the build of the selector tables
+initialize
{
SelectorItem *cur;
for (cur = emacsMetaKeys; cur->charCode; cur++)
{
cur->selector = sel_getUid(cur->selectorString);
cur->positionSelector = sel_getUid(cur->positionSelectorString);
}
for (cur = emacsControlKeys; cur->charCode; cur++)
{
cur->selector = sel_getUid(cur->selectorString);
cur->positionSelector = sel_getUid(cur->positionSelectorString);
}
return self;
}
- (int)positionForLineBeginActual
/* Not currently in use. Looks for newline to find actual paragraph begin.
*/
{
NXStream *s = [self stream];
int pos;
int ch;
if (spN.cp < 0) return 0; // Is this the right thing to do here?
NXSeek(s, sp0.cp, NX_FROMSTART);
while (((ch = GetPrevious(s)) != EOF) && (ch != '\n'));
pos = NXTell(s);
if (ch != EOF) pos++;
return pos;
}
- (int)positionForLineEndActual
/* Not currently in use. Looks for newline to find actual paragraph end.
*/
{
NXStream *s = [self stream];
int pos;
int ch;
int max = [self textLength];
if (spN.cp < 0) return 0;
if (spN.cp > max) return max;
NXSeek(s, spN.cp, NX_FROMSTART);
while (((ch = NXGetc(s)) != EOF) && (ch != '\n'));
pos = NXTell(s);
if (ch != EOF) pos--;
return pos;
}
- (int)positionForLineEndVisual
/* This uses the break array to find the visual line end.
* However, it subtracts one from the position because of that behavior
* of the Text object that makes the position at the end of one line
* the same character position a the beginning of next line. Seems to
* be no way to position the insertion point at the end of the line.
* Bummer.
*/
{
int lineLength;
int line;
line = (spN.line /sizeof(NXLineDesc));
lineLength = theBreaks->breaks[line] & 0x3fff;
lineLength--; // Notice the hack....
return (spN.c1st + lineLength);
}
- (int)positionForLineBeginVisual
{
return (sp0.c1st);
}
/** BIG FAT HAIRY NOTE
* This is how to change CTRL-A, CTRL-E, CTRL-K to use paragraph ends
* (actual newlines) instead of visual line breaks. Have the position
* for line end methods call to the position for actual rather than
* visual.
*/
- (int)positionForLineBegin
{
return [self positionForLineBeginVisual];
}
- (int)positionForLineEnd
{
return [self positionForLineEndVisual];
}
- (int)nextPositionIfEmpty
{
if (sp0.cp == spN.cp)
return spN.cp + 1;
else
return spN.cp;
}
/* This is my quick decision on what characters count as a word, and which
* don't. The correct way to do this is to parse the ClickTable, but the
* documentation is so incredibly sparse on this one....
*/
#define NORMAL_CHAR(ch) (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) ||((ch >= '0') && (ch <= '9')) || (ch == '\'')|| (ch == '_'))
- (int)positionForWordEnd
{
NXStream *s = [self stream];
int pos;
int ch;
int max = [self textLength];
if (spN.cp < 0) return 0; // boundary conditions? Is this right idea?
if (spN.cp > max) return max;
NXSeek(s, spN.cp, NX_FROMSTART);
while ((ch = NXGetc(s)) != EOF && !NORMAL_CHAR(ch)); // skip white space
while ((ch = NXGetc(s)) != EOF && NORMAL_CHAR(ch)); // jump normal chars
pos = NXTell(s);
if (ch != EOF) pos--;
return pos;
}
- (int)positionForWordBegin
{
NXStream *s = [self stream];
int pos;
int ch;
int max = [self textLength];
if (spN.cp < 0) return 0; // boundary conditions? Is this right idea?
if (spN.cp > max) return max;
NXSeek(s, sp0.cp, NX_FROMSTART);
while ((ch = GetPrevious(s)) != EOF && !NORMAL_CHAR(ch)); // skip white space
while ((ch = GetPrevious(s)) != EOF && NORMAL_CHAR(ch)); // jump normal chars
pos = NXTell(s);
if (ch != EOF) pos++;
return pos;
}
- (int) positionForDocumentEnd
{
return [self textLength];
}
- (int) positionForDocumentBegin
{
return 0;
}
- moveToPosition:(SEL)command
{
int pos;
pos = (int)[self perform:command];
[self setSel:pos :pos];
[self scrollSelToVisible];
return self;
}
- deleteToPosition:(SEL)command
/* Entry point for delete forward/backward word
*/
{
int pos;
int start,end;
pos = (int)[self perform:command];
if (pos > spN.cp)
{ // if position extends to the right
start = sp0.cp;
end = pos;
}
else
{ // else position extends to the left
start = pos;
end = spN.cp;
}
[self delete:start :end];
return self;
}
- delete:(int)start :(int)end
/* Entry point for all deletes done for Emacs bindings. Turns off
* autodisplay to avoid flicker and other unwanted drawing artifacts.
* Calls cut and uses the Pasteboard to implement yank. It is possible
* to implement separate Emacs kill buffer, but it would be a bit of
* hassle, because you need a Change object to keep both the runs and
* the text that is yanked. You can do it quick by storing only ASCII
* text, which is not a good idea. (Actually, to be correct, this is
* all that Edit does, but who wants to use Edit for a role model?)
*/
{
if (end - start)
{
[self setAutodisplay:NO];
[self setSel:start :end];
[self cut:self];
[[self setAutodisplay:YES] display];
}
return self;
}
- deleteToLineEnd
/* Somewhat icky hack has to handle the special case for deleting at end
* of line. If in middle of line, don't delete the new line. If at the
* very end of the line, do delete the new line.
*/
{
int pos;
int start,end;
pos = [self positionForLineEnd];
start = sp0.cp;
end = pos;
if (start == end) {// If already at end of line
int line;
int endsWithNewLine;
line = (spN.line /sizeof(NXLineDesc));
endsWithNewLine = theBreaks->breaks[line] & 0x4000;
if (endsWithNewLine)
end++;
else // Bail on case where at visual end of line, but no newline
return self;
}
[self delete:start :end];
return self;
}
- yank
{
[self paste:self];
return self;
}
- (BOOL) emacsEvent:(NXEvent *)event
{
SelectorItem *cur;
unsigned charCode = event->data.key.charCode;
if (event->flags & NX_CONTROLMASK)
{
cur = emacsControlKeys;
while (cur->charCode && (cur->charCode != charCode)) cur++;
if (cur->charCode)
{
[self perform:cur->selector
withSel:(cur->positionSelector? cur->positionSelector : 0)];
return YES;
}
}
if (event->flags & NX_ALTERNATEMASK)
{
cur = emacsMetaKeys;
while (cur->charCode && (cur->charCode != charCode)) cur++;
if (cur->charCode)
{
[self perform:cur->selector
withSel:(cur->positionSelector? cur->positionSelector : 0)];
return YES;
}
}
return NO;
}
- keyDown:(NXEvent *)event
{
if ([self emacsEvent:event])
return self;
else
return [super keyDown:event];
}
- (int)perform:(SEL)selector withSel:(SEL)helper
{
int (*func)(id,SEL,SEL);
func = (int (*)(id,SEL,SEL))[self methodFor:selector];
return (* func)(self, selector, helper);
}
- initFrame:(NXRect *)fRect
{
NXRect r = *fRect;
[super initFrame:fRect];
[self setMonoFont:NO];
[self setBackgroundGray:NX_WHITE];
[self setOpaque:YES];
[self setCharFilter:(NXCharFilterFunc)emacsFilter];
[self notifyAncestorWhenFrameChanged: YES];
[self setVertResizable:YES];
[self setMinSize:&r.size];
r.size.height = 1.0e30;
[self setMaxSize:&r.size];
return self;
}
@end